package com.jelly.config.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.google.common.collect.Maps;
import com.jelly.common.util.back.FilterChainDefinitionsUtil;
import com.jelly.common.util.back.ShiroUtil;
import com.jelly.config.redis.RedisCacheManager;
import com.jelly.config.redis.RedisManager;
import com.jelly.config.redis.session.RedisSessionDAO;
import com.jelly.config.shiro.filter.*;
import com.jelly.config.shiro.realm.MyShiroRealm;
import com.jelly.config.thymeleaf.ThymeleafView;
import com.jelly.config.xss.XSSFilter;
import com.jelly.web.modules.system.permission.service.IPermissionService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * <h4>功能:【shiro配置类】【创建人：qiaoliang】</h4>
 *
 * @时间:2018/7/2 0002 9:51
 * @备注:<h4>
 *  1.LifecycleBeanPostProcessor，这是个DestructionAwareBeanPostProcessor的子类，负责org.apache.shiro.util.Initializable类型bean的生命周期的，初始化和销毁。主要是AuthorizingRealm类的子类，以及EhCacheManager类。
 *  2.HashedCredentialsMatcher，这个类是为了对密码进行编码的，防止密码在数据库里明码保存，当然在登陆认证的生活，这个类也负责对form里输入的密码进行编码。
 *  3.ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm，负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
 *  4.EhCacheManager，缓存管理，用户登陆成功后，把用户信息和权限信息缓存起来，然后每次用户请求时，放入用户的session中，如果不设置这个bean，每个请求都会查询一次数据库。
 *  5.SecurityManager，权限管理，这个类组合了登陆，登出，权限，session的处理，是个比较重要的类。
 *  6.ShiroFilterFactoryBean，是个factorybean，为了生成ShiroFilter。它主要保持了三项数据，securityManager，filters，filterChainDefinitionManager。
 *  7.DefaultAdvisorAutoProxyCreator，Spring的一个bean，由Advisor决定对哪些类的方法进行AOP代理。
 *  8.AuthorizationAttributeSourceAdvisor，shiro里实现的Advisor类，内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
 * </h4>
 */
@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
public class ShiroConfig {

    // 地址
    @Value("${redis.host}")
    private String host;
    // 端口
    @Value("${redis.port}")
    private int port;
    // 缓存过期时间
    @Value("${redis.expire}")
    private int expire;
    //
    @Value("${redis.timeout}")
    private int timeout;
    // 密码
    @Value("${redis.password}")
    private String password;

    // 密码加密规则
    @Value("${shiro.hashedCredentialsMatcher.hashAlgorithmName}")
    private String hashAlgorithmName;
    //散列的次数，比如散列两次，相当于 md5(md5(""));
    @Value("${shiro.hashedCredentialsMatcher.hashIterations}")
    private int hashIterations;

    // Shiro sessionId名称
    @Value("${shiro.session.sessionIdCookie.name}")
    private String sessionIdCookie;
    // 这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
    @Value("${shiro.session.rememberMeCookie.name}")
    private String rememberMeCookieName;
    // 记住我cookie生效时间30天 ,单位秒
    @Value("${shiro.session.rememberMeCookie.maxAge}")
    private int maxAge;

    @Autowired
    private IPermissionService permissionService;

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 没有登陆的用户只能访问登陆页面
        shiroFilterFactoryBean.setLoginUrl("/login");

        //自定义拦截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();

        //限制同一帐号同时在线的个数。
        filtersMap.put("kickout", sessionControlFilter());

        filtersMap.put("role",roleFilter());
        filtersMap.put("remUrlId",removeUrlJsessionIdFilter());
        filtersMap.put("permission",permissionFilter());
        filtersMap.put("code",vcodeFilter());
        filtersMap.put("userFilter",userFilter());
        filtersMap.put("testFilter",testFilter());

        shiroFilterFactoryBean.setFilters(filtersMap);

        Map<String,String> otherChains = new LinkedHashMap<String,String>();

        //加载配置默认的过滤链
        otherChains.putAll(FilterChainDefinitionsUtil.loadFilterChainDefinitions(permissionService.loadFilterChainDefinitions()));

        shiroFilterFactoryBean.setFilterChainDefinitionMap(otherChains);

        return shiroFilterFactoryBean;

    }

    @Bean
    public SecurityManager securityManager(MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myShiroRealm);
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());

        //注入安全管理器，里面包含了大部分信息，比较重要;
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

    /**
     * cookie对象;
     * rememberMeCookie()方法是设置Cookie的生成模版，比如cookie的name，cookie的有效时间等等。
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        SimpleCookie simpleCookie = new SimpleCookie(rememberMeCookieName);
        simpleCookie.setMaxAge(maxAge);
        return simpleCookie;
    }

    /**
     * cookie管理对象;
     * rememberMeManager()方法是生成rememberMe管理器，而且要将这个rememberMe管理器设置到securityManager中
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return cookieRememberMeManager;
    }

    /**
     * 身份认证realm; (这个需要自己写，账号密码校验；权限等)
     *
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        myShiroRealm.setCacheManager(cacheManager());
        myShiroRealm.setAuthenticationCachingEnabled(false);
        myShiroRealm.setAuthorizationCachingEnabled(false);
        return myShiroRealm;
    }


    /**
     * 凭证匹配器
     * （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * ）
     * @return
     */
    @Bean
    public RetryLimitCredentialsMatcher  hashedCredentialsMatcher() {
        RetryLimitCredentialsMatcher hashedCredentialsMatcher = new RetryLimitCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        //散列的次数，比如散列两次，相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(hashIterations);

        hashedCredentialsMatcher.setRedisManager(redisManager());

        return hashedCredentialsMatcher;
    }

    /**
     * cacheManager 缓存 redis实现
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(expire);// 配置缓存过期时间
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }


    /**
     * Session Manager
     */
    @Bean("sessionManager")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdCookie(sessionIdCookie());
        return sessionManager;
    }

    /**
     * Session Manager
     */
    @Bean
    public SimpleCookie sessionIdCookie() {
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName(sessionIdCookie);
        return simpleCookie;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /***
     * 授权所用配置
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /***
     * 使授权注解起作用不如不想配置可以在pom文件中加入
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 限制同一账号登录同时登录人数控制
     *
     * @return
     */
    @Bean
    public SessionControlFilter sessionControlFilter() {
        SessionControlFilter kickoutSessionControlFilter = new SessionControlFilter();
        kickoutSessionControlFilter.setCacheManager(cacheManager());
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        kickoutSessionControlFilter.setKickoutAfter(false);
        kickoutSessionControlFilter.setMaxSession(1);
        kickoutSessionControlFilter.setKickoutUrl("/kickout");
        return kickoutSessionControlFilter;
    }

    @Bean
    public ThymeleafView thymeleafView(){
        return new ThymeleafView();
    }
    /**
     * 注入配置信息
     * @return
     */
    @Bean(name = "shiroUtil")
    public ShiroUtil redisUtil() {
        ShiroUtil redisUtil = new ShiroUtil();
        redisUtil.setHashAlgorithmName(hashAlgorithmName);
        redisUtil.setHashIterations(hashIterations);
        return redisUtil;
    }

    /**
     * shiro标签的使用
     *
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * 自定义角色过滤器
     *
     * @return
     */
    @Bean
    public RoleFilter roleFilter() {
        return new RoleFilter();
    }

    /**
     * 演示模式过滤器
     *
     * @return
     */
    @Bean
    public TestFilter testFilter() {
        return new TestFilter();
    }

    /**
     * sessionId删除过滤器
     *
     * @return
     */
    @Bean
    public RemoveUrlJsessionIdFilter removeUrlJsessionIdFilter(){
        return new RemoveUrlJsessionIdFilter();
    }

    /**
     * 自定义权限过滤器
     *
     * @return
     */
    @Bean
    public PermissionFilter permissionFilter(){
        return new PermissionFilter();
    }

    /**
     * 验证码过滤器
     *
     * @return
     */
    @Bean
    public VcodeFilter vcodeFilter(){
        return new VcodeFilter();
    }

    /**
     * 用户过滤器
     *
     * @return
     */
    @Bean
    public UserFilter userFilter(){
        UserFilter userFilter = new UserFilter();
        userFilter.setCacheManager(cacheManager());
        return userFilter;
    }

    /**
     * xss过滤拦截器
     */
    @Bean
    public FilterRegistrationBean xssFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new XSSFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.addUrlPatterns("/admin/*/*/update","/admin/*/*/add","/share/*/update");
        Map<String, String> initParameters = Maps.newHashMap();
        initParameters.put("excludes", "/admin/basics/bulletin/*");
        initParameters.put("isIncludeRichText", "false");
        filterRegistrationBean.setInitParameters(initParameters);
        return filterRegistrationBean;
    }

}